package main

import (
	"errors"
	"fmt"
	"github.com/logrusorgru/aurora"
	"github.com/tal-tech/go-zero/core/logx"
	apiformat "github.com/tal-tech/go-zero/tools/goctl/api/format"
	"github.com/tal-tech/go-zero/tools/goctl/api/gogen"
	"github.com/tal-tech/go-zero/tools/goctl/api/parser"
	apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util"
	"github.com/tal-tech/go-zero/tools/goctl/config"
	conf "github.com/tal-tech/go-zero/tools/goctl/config"
	"github.com/tal-tech/go-zero/tools/goctl/util"
	ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
	"github.com/tal-tech/go-zero/tools/goctl/util/ctx"
	"github.com/urfave/cli"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"text/template"
	"time"
)

const (
	category        = "newapi"
	apiTemplateFile = "newtemplate.tpl"
)

const apiTemplate = `
syntax = "v1"

info(
	title: ""
	desc:  ""
	author: "{{.gitUser}}"
	email: "{{.gitEmail}}"
)

type request {
	requestId string ` + "`" + `json:"requestId"` + "`" + `
}

type response {
	code    int 		` + "`" + `json:"code"` + "`" + `
	msg 	string		` + "`" + `json:"message"` + "`" + `
	data    interface{} ` + "`" + `json:"data"` + "`" + `
}

service {{.name}}-api {
	@doc(
		summary :""
	)
	@handler GetUser 
	get /from/:name(Request) returns(Response)
	
	@doc(
		summary :""
	)
	@handler CreateUser 
	post /users/create(request) returns(Response)
}
`

const apiTemplate2 = `
syntax = "v1"

info(
	title: ""
	desc:  ""
	author: "{{.gitUser}}"
	email: "{{.gitEmail}}"
)

type request {
	requestId string ` + "`" + `json:"requestId"` + "`" + `
}

type response {
	code    int 		` + "`" + `json:"code"` + "`" + `
	msg 	string		` + "`" + `json:"message"` + "`" + `
	data    interface{} ` + "`" + `json:"data"` + "`" + `
}

service {{.serviceName}} {
	@doc(
		summary :""
	)
	@handler GetUser 
	get /users/id/:userId(request) returns(response)

	@doc(
		summary :""
	)
	@handler CreateUser 
	post /users/create(request) returns(response)
}
`

const (
	markdownTemplate = `
### {{.index}}. {{.routeComment}}

1. 路由定义

- Url: {{.uri}}
- Method: {{.method}}
- Request: {{.requestType}}
- Response: {{.responseType}}

2. 请求定义
{{.requestContent}}

3. 返回定义
{{.responseContent}}  

`
)

// New fast create service
func New(c *cli.Context) error {
	args := c.Args()
	dirName := args.First()
	if len(dirName) == 0 {
		dirName = "greet"
	}
	if strings.Contains(dirName, "-") {
		return errors.New("api new command service name not support strikethrough, because this will used by function name")
	}
	abs, err := filepath.Abs(dirName)
	if err != nil {
		return err
	}
	err = util.MkdirIfNotExist(abs)
	if err != nil {
		return err
	}
	dirName = filepath.Base(filepath.Clean(abs))
	filename := dirName + ".api"
	apiFilePath := filepath.Join(abs, filename)
	fp, err := os.Create(apiFilePath)
	if err != nil {
		return err
	}
	defer fp.Close()

	text, err := util.LoadTemplate(category, apiTemplateFile, apiTemplate)
	if err != nil {
		return err
	}

	t := template.Must(template.New("template").Parse(text))
	if err := t.Execute(fp, map[string]string{
		"name":     dirName,
		"handler":  strings.Title(dirName),
		"gitUser":  GetGitName(),
		"gitEmail": GetGitEmail(),
	}); err != nil {
		return err
	}

	err = gogen.DoGenProject(apiFilePath, abs, conf.DefaultFormat)
	return err
}

func GetGitName() string {
	cmd := exec.Command("git", "config", "user.name")
	out, err := cmd.CombinedOutput()
	if err != nil {
		return ""
	}

	return strings.TrimSpace(string(out))
}

func GetGitEmail() string {
	cmd := exec.Command("git", "config", "user.email")
	out, err := cmd.CombinedOutput()
	if err != nil {
		return ""
	}

	return strings.TrimSpace(string(out))
}

// ApiCommand create api template file
func Api(c *cli.Context) error {
	apiFile := c.String("o")
	if len(apiFile) == 0 {
		return errors.New("missing -o")
	}
	apiFile2 := c.String("new")
	dir := c.String("dir")

	fp, err := util.CreateIfNotExist(apiFile)
	if err != nil {
		return err
	}
	defer fp.Close()
	text, err := util.LoadTemplate(category, apiTemplateFile, apiTemplate2)
	if err != nil {
		return err
	}

	baseName := util.FileNameWithoutExt(filepath.Base(apiFile))
	if strings.HasSuffix(strings.ToLower(baseName), "-api") {
		baseName = baseName[:len(baseName)-4]
	} else if strings.HasSuffix(strings.ToLower(baseName), "api") {
		baseName = baseName[:len(baseName)-3]
	}

	t := template.Must(template.New("etcTemplate").Parse(text))
	if err := t.Execute(fp, map[string]string{
		"gitUser":     GetGitName(),
		"gitEmail":    GetGitEmail(),
		"serviceName": baseName + "-api",
	}); err != nil {
		return err
	}
	fmt.Println(aurora.Green("Done."))
	if len(apiFile2) == 0 {
		return errors.New("missing -api")
	}
	if len(dir) == 0 {
		return errors.New("missing -dir")
	}
	namingStyle := c.String("style")
	return DoGenProject(apiFile2, dir, namingStyle)
}

var tmpDir = path.Join(os.TempDir(), "goctl")

// DoGenProject gen go project files with api file
func DoGenProject(apiFile, dir, style string) error {
	api, err := parser.Parse(apiFile)
	if err != nil {
		return err
	}

	cfg, err := config.NewConfig(style)
	if err != nil {
		return err
	}

	logx.Must(util.MkdirIfNotExist(dir))
	rootPkg, err := getParentPackage(dir)
	if err != nil {
		return err
	}

	logx.Must(genEtc(dir, cfg, api))
	logx.Must(genConfig(dir, cfg, api))
	logx.Must(genMain(dir, rootPkg, cfg, api))
	logx.Must(genServiceContext(dir, rootPkg, cfg, api))
	logx.Must(genTypes(dir, cfg, api))
	logx.Must(genRoutes(dir, rootPkg, cfg, api))
	logx.Must(genHandlers(dir, rootPkg, cfg, api))
	logx.Must(genLogic(dir, rootPkg, cfg, api))
	logx.Must(genMiddleware(dir, cfg, api))

	if err := backupAndSweep(apiFile); err != nil {
		return err
	}

	if err := apiformat.ApiFormatByPath(apiFile); err != nil {
		return err
	}

	fmt.Println(aurora.Green("Done."))
	return nil
}

func backupAndSweep(apiFile string) error {
	var err error
	var wg sync.WaitGroup

	wg.Add(2)
	_ = os.MkdirAll(tmpDir, os.ModePerm)

	go func() {
		_, fileName := filepath.Split(apiFile)
		_, e := apiutil.Copy(apiFile, fmt.Sprintf(path.Join(tmpDir, tmpFile), fileName, time.Now().Unix()))
		if e != nil {
			err = e
		}
		wg.Done()
	}()
	go func() {
		if e := sweep(); e != nil {
			err = e
		}
		wg.Done()
	}()
	wg.Wait()

	return err
}

func filePathWalkDir(root string) ([]string, error) {
	var files []string
	err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() && strings.HasSuffix(path, ".api") {
			files = append(files, path)
		}
		return nil
	})
	return files, err
}

func sweep() error {
	keepTime := time.Now().AddDate(0, 0, -7)
	return filepath.Walk(tmpDir, func(fpath string, info os.FileInfo, err error) error {
		if info.IsDir() {
			return nil
		}

		pos := strings.LastIndexByte(info.Name(), '-')
		if pos > 0 {
			timestamp := info.Name()[pos+1:]
			seconds, err := strconv.ParseInt(timestamp, 10, 64)
			if err != nil {
				// print error and ignore
				fmt.Println(aurora.Red(fmt.Sprintf("sweep ignored file: %s", fpath)))
				return nil
			}

			tm := time.Unix(seconds, 0)
			if tm.Before(keepTime) {
				if err := os.Remove(fpath); err != nil {
					fmt.Println(aurora.Red(fmt.Sprintf("failed to remove file: %s", fpath)))
					return err
				}
			}
		}
		return nil
	})
}

func getParentPackage(dir string) (string, error) {
	abs, err := filepath.Abs(dir)
	if err != nil {
		return "", err
	}

	projectCtx, err := ctx.Prepare(abs)
	if err != nil {
		return "", err
	}

	// fix https://github.com/zeromicro/go-zero/issues/1058
	wd := projectCtx.WorkDir
	d := projectCtx.Dir
	same, err := ctlutil.SameFile(wd, d)
	if err != nil {
		return "", err
	}

	trim := strings.TrimPrefix(projectCtx.WorkDir, projectCtx.Dir)
	if same {
		trim = strings.TrimPrefix(strings.ToLower(projectCtx.WorkDir), strings.ToLower(projectCtx.Dir))
	}

	return filepath.ToSlash(filepath.Join(projectCtx.Path, trim)), nil
}

